import util from '@ohos.util';
import ble from '@ohos.bluetooth.ble';
import { logger } from '../core/logger';
import socket from '@ohos.bluetooth.socket';
import syncClient from '../apis/syncClient';
import { mqttType } from '../enums/mqttType';
import { mqttData } from '../models/mqttData';
import { globalData } from '../core/globalData';
import { uploadInfo } from '../models/uploadInfo';
import { deviceInfo } from '../models/deviceInfo';
import { settingService } from './settingService';
import { getTime, showPopup } from '../core/utils';

export class nearLinkService {
  private readonly uuid: string;
  private serverNumber: number = 0;
  private serviceUuid: string = '';
  private clientNumber: number = -1;
  private device: ble.GattClientDevice;
  private readonly encoder: util.TextEncoder;
  private readonly decoder: util.TextDecoder;
  private characteristics: Map<string, ble.GattWriteType> = new Map();

  constructor() {
    this.uuid = util.generateRandomUUID(false);
    this.encoder = new util.TextEncoder();
    this.decoder = util.TextDecoder.create();
  }

  async connect() {
    const deviceId = settingService.instance.bluetoothId;
    this.createSpp(deviceId);
    await this.connectGatt(deviceId);
  }

  private createSpp(deviceId: string) {
    try {
      const sppOption: socket.SppOptions = { uuid: this.uuid, secure: false, type: 0 };
      socket.sppListen('sasServer', sppOption, (_, number) => {
        this.serverNumber = number;
        socket.sppAccept(this.serverNumber, (_, number) => {
          this.clientNumber = number;
        });
        socket.sppConnect(deviceId, sppOption, (err, number) => {
          if (err) {
            logger.instance.error(err);
          } else {
            this.clientNumber = number;
            socket.on('sppRead', this.clientNumber, async (buffer: ArrayBuffer) => {
              const json = this.decoder.decodeWithStream(new Uint8Array(buffer));
              const obj: mqttData = JSON.parse(json);
              if (obj && obj.areaCode.trim() == globalData.currentArea.code) {
                const info: uploadInfo = {
                  deviceId: obj.deviceId,
                  temperature: obj.data.temperature,
                  humidity: obj.data.humidity,
                  illumination: obj.data.illumination,
                  createDate: new Date()
                };
                let ret = await syncClient.uploadData([info]);
                if (ret.success)
                  showPopup('上传成功');
                else
                  showPopup('上传失败，' + ret.message);
              }
            });
          }
        });
      });
    } catch (err) {
      logger.instance.error(err);
    }
  }

  private async connectGatt(deviceId: string) {
    try {
      this.device = ble.createGattClientDevice(deviceId);
      this.device.on('BLECharacteristicChange', async (data) => {
        const json = this.decoder.decodeWithStream(new Uint8Array(data.characteristicValue));
        const obj: mqttData = JSON.parse(json);
        if (obj && obj.areaCode.trim() == globalData.currentArea.code) {
          const info: uploadInfo = {
            createDate: new Date(),
            deviceId: obj.deviceId,
            humidity: obj.data.humidity,
            temperature: obj.data.temperature,
            illumination: obj.data.illumination
          };
          let ret = await syncClient.uploadData([info]);
          if (ret.success)
            showPopup('上传成功');
          else
            showPopup('上传失败，' + ret.message);
        }
      });
      this.device.connect();
      const services = await this.device.getServices();
      for (const service of services) {
        for (const item of (service.characteristics ?? [])) {
          if (item.properties.writeNoResponse)
            this.characteristics.set(item.characteristicUuid, ble.GattWriteType.WRITE_NO_RESPONSE);
          else if (item.properties.write)
            this.characteristics.set(item.characteristicUuid, ble.GattWriteType.WRITE);
        }
        if (this.characteristics.size > 0) {
          this.serviceUuid = service.serviceUuid;
          break;
        }
      }
    } catch (err) {
      logger.instance.error(err);
    }
  }

  sendDevices(devices: deviceInfo[]) {
    try {
      const data: mqttData = {
        deviceId: '',
        time: getTime(),
        areaCode: globalData.currentArea.code,
        data: {
          type: mqttType.online,
          devices: devices
        }
      };
      this.sendPayload(data.toString());
    } catch (err) {
      logger.instance.error(err);
    }
  }

  sendPayload(data: string) {
    this.sendSppData(data);
    this.sendGattData(data);
  }

  private sendSppData(data: mqttData | string) {
    try {
      if (data) {
        const payload = typeof data === 'string' ? data : JSON.stringify(data);
        const sendData = this.encoder.encodeInto(payload);
        socket.sppWrite(this.clientNumber, sendData.buffer);
      }
    } catch (err) {
      logger.instance.error(err);
    }
  }

  private sendGattData(data: mqttData | string) {
    try {
      if (data) {
        const payload = typeof data === 'string' ? data : JSON.stringify(data);
        const sendData = this.encoder.encodeInto(payload);
        for (const item of this.characteristics) {
          let characteristic: ble.BLECharacteristic = {
            descriptors: [],
            characteristicUuid: item[0],
            serviceUuid: this.serviceUuid,
            characteristicValue: sendData.buffer
          };
          this.device.writeCharacteristicValue(characteristic, item[1]);
        }
      }
    } catch (err) {
      logger.instance.error(err);
    }
  }

  close() {
    this.closeSpp();
    this.closeGatt();
  }

  private closeSpp() {
    try {
      socket.off('sppRead', this.clientNumber);
      socket.sppCloseClientSocket(this.clientNumber);
      socket.sppCloseServerSocket(this.serverNumber);
    } catch (err) {
      logger.instance.error(err);
    }
  }

  private closeGatt() {
    try {
      if (this.device) {
        this.device.off('BLECharacteristicChange');
        this.device.disconnect();
        this.device.close();
      }
    } catch (err) {
      logger.instance.error(err);
    }
  }
}